﻿//------------------------------------------------------------------------------
// <copyright file="RpcArchive.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//------------------------------------------------------------------------------

namespace Microsoft.Internal.GamesTest.Rpc.Client
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.IO;
    using System.Linq;
    using System.Runtime.Serialization;
    using System.Runtime.Serialization.Json;
    using Microsoft.Internal.GamesTest.Rpc.Client.Telemetry;

    /// <summary>
    /// This class is the data container used to send and receive data to and from
    /// an RpcServer.
    /// </summary>
    [Serializable]
    public class RpcArchive : ISerializable, IConvertible
    {
        /// <summary>
        /// The dictionary that stores named values for the archive, serialied to JSON when needed.
        /// </summary>
        private Dictionary<string, object> data;

        /// <summary>
        /// The dictionary that stores named value types.
        /// </summary>
        /// <remarks>
        /// Maintaining meta-data for stored values is important when parsing archives from JSON, because
        /// JSON types are not indicative of the original type before the values were put in the archive.
        /// Meta-data itself is passed in a hidden array and decoded to support later queries about item types.
        /// </remarks>
        private Dictionary<string, Type> metaData;

        /// <summary>
        /// Initializes a new instance of the RpcArchive class.
        /// </summary>
        public RpcArchive()
        {
            this.data = new Dictionary<string, object>();
            this.metaData = new Dictionary<string, Type>();
            this.ArchiveGuid = Guid.NewGuid();

            RpcClientEventSource.Logger.ArchiveConstructed(RpcClientEventSource.GetCurrentConstructor(false), this.ArchiveGuid);
        }

        /// <summary>
        /// Initializes a new instance of the RpcArchive class.
        /// This constructor is implicitly required by the ISerializable interface.
        /// </summary>
        /// <param name="information">The System.Runtime.Serialization.SerializationInfo to use to populate the new RpcArchive.</param>
        /// <param name="context">The source (see System.Runtime.Serialization.StreamingContext) for this deserialization.</param>
        protected RpcArchive(SerializationInfo information, StreamingContext context)
            : this()
        {
            if (information == null)
            {
                throw new ArgumentNullException("information");
            }

            foreach (var item in information)
            {
                if (item.Name == "__ftype")
                {
                    // Parse meta-data for all archive items recorded in a hidden array
                    try
                    {
                        // The meta-data is stored as field name followed by field value encoded as integer converted to string
                        // The field type has to correspond to FieldTypes, as both client and server use the same enumeration.
                        string fieldName = null;

                        foreach (var meta in item.Value as object[])
                        {
                            if (fieldName == null)
                            {
                                fieldName = meta.ToString();
                            }
                            else
                            {
                                this.metaData[fieldName] = this.FieldTypeToType((FieldType)int.Parse(meta.ToString(), CultureInfo.InvariantCulture));
                                fieldName = null;
                            }
                        }
                    }
                    catch (ArgumentNullException ex)
                    {
                        throw new RpcProtocolFormatViolationException("The Archive meta-data is corrupted.", ex);
                    }
                    catch (FormatException ex)
                    {
                        throw new RpcProtocolFormatViolationException("The Archive meta-data is corrupted.", ex);
                    }
                    catch (OverflowException ex)
                    {
                        throw new RpcProtocolFormatViolationException("The Archive meta-data is corrupted.", ex);
                    }
                }
                else
                {
                    // Parse archive item
                    this.data[item.Name] = item.Value;
                }
            }

            this.ArchiveGuid = Guid.NewGuid();
            RpcClientEventSource.Logger.ArchiveConstructed(RpcClientEventSource.GetCurrentConstructor(false), this.ArchiveGuid);
        }

        /// <summary>
        /// Field types in the archive.
        /// </summary>
        /// <remarks>
        /// The order and values of the enumeration items must match those defined in RpcServerImpl.h.
        /// </remarks>
        private enum FieldType
        {
            /// <summary>
            /// Unknown or doesn't exist, used to indicate an error.
            /// </summary>
            Unknown = 0,

            /// <summary>
            /// Embedded Archive.
            /// </summary>
            Archive,

            /// <summary>
            /// String value.
            /// </summary>
            String,

            /// <summary>
            /// Boolean value.
            /// </summary>
            Bool,

            /// <summary>
            /// Signed 8-bit value.
            /// </summary>
            Int8,

            /// <summary>
            /// Unsigned 8-bit value.
            /// </summary>
            UInt8,

            /// <summary>
            /// Signed 8-bit value, or equivalent 16-bit C# character (only if up-converting).
            /// </summary>
            Char,

            /// <summary>
            /// Unsigned 16-bit value.
            /// </summary>
            UShort,

            /// <summary>
            /// Signed 16-bit value.
            /// </summary>
            Short,

            /// <summary>
            /// Signed 32-bit value.
            /// </summary>
            Int,

            /// <summary>
            /// Unsigned 32-bit value.
            /// </summary>
            UInt,

            /// <summary>
            /// Signed 32-bit value.
            /// </summary>
            Long,

            /// <summary>
            /// Unsigned 32-bit value.
            /// </summary>
            ULong,

            /// <summary>
            /// Signed 64-bit value.
            /// </summary>
            Int64,

            /// <summary>
            /// Unsigned 64-bit value.
            /// </summary>
            UInt64,

            /// <summary>
            /// Floating point value.
            /// </summary>
            Float,

            /// <summary>
            /// Double value.
            /// </summary>
            Double,

            /// <summary>
            /// Binary large object value.
            /// </summary>
            Blob
        }

        /// <summary>
        /// Gets an enumeration of fields stored in the archive.
        /// </summary>
        public IEnumerable<RpcArchiveField> Fields
        {
            get
            {
                return this.metaData.Keys.Select(f => new RpcArchiveField(f, this.metaData[f]));
            }
        }

        /// <summary>
        /// Gets the archive guid used in telemetry.
        /// </summary>
        internal Guid ArchiveGuid { get; private set; }

        /// <summary>
        /// Gets the number of parameters in the archive for telemetry.
        /// </summary>
        internal int ParameterCount
        {
            get
            {
                return this.data.Count;
            }
        }

        /// <summary>
        /// Gets the value of a named field within this RpcArchive.
        /// </summary>
        /// <typeparam name="T">The type of the field that is expected to be returned.</typeparam>
        /// <param name="fieldName">The name of the field to be retrieved.</param>
        /// <returns>The value of the field with the given name.</returns>
        /// <exception cref="RpcArchiveFieldNotFoundException">Thrown if a field with the given name cannot be found.</exception>
        public T GetNamedValue<T>(string fieldName)
        {
            object fieldValue;
            if (!this.data.TryGetValue(fieldName, out fieldValue))
            {
                throw new RpcArchiveFieldNotFoundException(fieldName);
            }

            // Note that if "fieldValue" is an RpcArchive the following call will invoke the "IConvertible.ToType" method
            // on "fieldValue".
            return (T)System.Convert.ChangeType(fieldValue, typeof(T), CultureInfo.InvariantCulture);
        }

        /// <summary>
        /// Gets the collection value of a named field within this RpcArchive.
        /// </summary>
        /// <typeparam name="T">The generic type of IEnumerable type of the field that is expected to be returned.</typeparam>
        /// <param name="fieldName">The name of the field to be retrieved.</param>
        /// <returns>The collection value of the field with the given name.</returns>
        /// <exception cref="RpcArchiveFieldNotFoundException">Thrown if a field with the given name cannot be found.</exception>
        public IEnumerable<T> GetNamedCollection<T>(string fieldName)
        {
            object fieldValue;
            if (!this.data.TryGetValue(fieldName, out fieldValue))
            {
                throw new RpcArchiveFieldNotFoundException(fieldName);
            }

            // Return a collection if it is already strongly typed (for example when it is set manually using SetNamedCollection method)
            IEnumerable<T> typedCollection = fieldValue as IEnumerable<T>;
            if (typedCollection != null)
            {
                return typedCollection;
            }

            // DataContractJsonSerializer deserializes any collection without type information as object[]
            object[] collection = fieldValue as object[];
            if (collection != null)
            {
                Type type = typeof(T);

                // Internally, JSON stores numbers as doubles.  Doubles only have 16 digits of precision.
                // A 64-bit int can have up to 20 digits.  Therefore, to maintain fidelity, 64-bit ints are returned as strings.
                if (type == typeof(long))
                {
                    typedCollection = collection.Select(e => long.Parse((string)e, CultureInfo.InvariantCulture)).Cast<T>();
                }
                else if (type == typeof(ulong))
                {
                    typedCollection = collection.Select(e => ulong.Parse((string)e, CultureInfo.InvariantCulture)).Cast<T>();
                }
                else
                {
                    typedCollection = collection.Select(e => (T)System.Convert.ChangeType(e, type, CultureInfo.InvariantCulture));
                }

                return typedCollection.ToArray();
            }

            throw new InvalidCastException("The value is not of collection type and it cannot be returned as IEnumerable");
        }

        /// <summary>
        /// Gets a value indicating whether or not a field with the given name exists within this RpcArchive.
        /// </summary>
        /// <param name="fieldName">The name of the field to see if it exists within this RpcArchive.</param>
        /// <returns>True if a field with the given name exists within this RpcArchive, otherwise false.</returns>
        public bool HasNamedValue(string fieldName)
        {
            return this.data.ContainsKey(fieldName);
        }

        /// <summary>
        /// Sets the value for a field with the given name.
        /// </summary>
        /// <typeparam name="T">The data type of the field.</typeparam>
        /// <param name="fieldName">The name to be associated with the given value within the archive.</param>
        /// <param name="fieldValue">The value to be associated with the given name within the archive.</param>
        public void SetNamedValue<T>(string fieldName, T fieldValue)
        {
            if (fieldValue == null)
            {
                throw new ArgumentNullException("fieldValue");
            }

            if (fieldName == null)
            {
                throw new ArgumentNullException("fieldName");
            }

            // Record the meta-data in case an archive returned from server is re-used to send data to server in another call.
            this.metaData[fieldName] = typeof(T);

            if (typeof(T) == typeof(long) || typeof(T) == typeof(ulong))
            {
                // Internally, JSON stores numbers as doubles.  Doubles only have 16 digits of precision.
                // A 64-bit int can have up to 20 digits.  Therefore, to maintain fidelity, we need to store
                // 64-bit ints as strings.
                this.data[fieldName] = fieldValue.ToString();
            }
            else
            {
                this.data[fieldName] = fieldValue;
            }
        }

        /// <summary>
        /// Sets the collection value for a field with the given name.
        /// </summary>
        /// <typeparam name="T">The generic type of IEnumerable type of the field.</typeparam>
        /// <param name="fieldName">The name to be associated with the given collection value within the archive.</param>
        /// <param name="fieldValue">The collection value to be associated with the given name within the archive.</param>
        public void SetNamedCollection<T>(string fieldName, IEnumerable<T> fieldValue)
        {
            if (fieldValue == null)
            {
                throw new ArgumentNullException("fieldValue");
            }

            if (fieldName == null)
            {
                throw new ArgumentNullException("fieldName");
            }

            Type type = typeof(T);

            // Record the meta-data in case an archive returned from server is re-used to send data to server in another call.
            this.metaData[fieldName] = type;

            // Internally, JSON stores numbers as doubles.  Doubles only have 16 digits of precision.
            // A 64-bit int can have up to 20 digits.  Therefore, to maintain fidelity, we need to store
            // 64-bit ints as strings.
            if (type == typeof(long) || type == typeof(ulong))
            {
                this.data[fieldName] = fieldValue.Select(e => e.ToString()).ToArray();
                return;
            }

            // DataContractJsonSerializer serializes any collections and arrays of a specific type 
            // using the same data contract, but it does not allow in the KnownTypes collection to have different types 
            // that correspond to the same data contract.
            // E.g. both List(int) and int[] cannot be added to the KnownTypes collection of DataContractJsonSerializer
            // at the same time and we cannot serialize either of them if it is not added to the KnownTypes.
            // The solution is to have only array types in the KnownTypes collection, and substitute any other collections
            // IEnumerable(T) with an array of a corresponding type.

            // Arrays do not require type conversion for serialization (supported natively).
            if (fieldValue.GetType().IsArray)
            {
                this.data[fieldName] = fieldValue;
                return;
            }

            this.data[fieldName] = fieldValue.ToArray();
        }

        /// <summary>
        /// Convert this RpcArchive to the type given by <typeparamref name="T"/>.  This does not support primitive types.
        /// It is expected that type <typeparamref name="T"/> is decorated with the [DataContract] attribute
        /// and each of the members intended to be populated are decorated with the [DataMember] attribute.
        /// </summary>
        /// <typeparam name="T">The type to try to convert this RpcArchive into.</typeparam>
        /// <returns>An instance of type <typeparamref name="T"/> that has been deserialized from this archive.</returns>
        public T ToType<T>()
        {
            RpcClientEventSource.Logger.MethodCalled(RpcClientEventSource.GetCurrentMethod());

            return (T)((IConvertible)this).ToType(typeof(T), CultureInfo.InvariantCulture);
        }

        /// <summary>
        /// Populates a System.Runtime.Serialization.SerializationInfo with the data needed to serialize this object.
        /// </summary>
        /// <param name="info">The System.Runtime.Serialization.SerializationInfo to populate with data.</param>
        /// <param name="context">The destination (see System.Runtime.Serialization.StreamingContext) for this serialization.</param>
        void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
        {
            this.GetObjectData(info, context);
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Required by the IConvertible interface.")]
        TypeCode IConvertible.GetTypeCode()
        {
            return TypeCode.Object;
        }

        bool IConvertible.ToBoolean(IFormatProvider provider)
        {
            throw new RpcConversionFailureException("Cannot convert an RpcArchive to a boolean.", typeof(RpcArchive), typeof(bool));
        }

        byte IConvertible.ToByte(IFormatProvider provider)
        {
            throw new RpcConversionFailureException("Cannot convert an RpcArchive to a byte.", typeof(RpcArchive), typeof(byte));
        }

        char IConvertible.ToChar(IFormatProvider provider)
        {
            throw new RpcConversionFailureException("Cannot convert an RpcArchive to a char.", typeof(RpcArchive), typeof(char));
        }

        DateTime IConvertible.ToDateTime(IFormatProvider provider)
        {
            throw new RpcConversionFailureException("Cannot convert an RpcArchive to a DateTime.", typeof(RpcArchive), typeof(DateTime));
        }

        decimal IConvertible.ToDecimal(IFormatProvider provider)
        {
            throw new RpcConversionFailureException("Cannot convert an RpcArchive to a decimal.", typeof(RpcArchive), typeof(decimal));
        }

        double IConvertible.ToDouble(IFormatProvider provider)
        {
            throw new RpcConversionFailureException("Cannot convert an RpcArchive to a double.", typeof(RpcArchive), typeof(double));
        }

        short IConvertible.ToInt16(IFormatProvider provider)
        {
            throw new RpcConversionFailureException("Cannot convert an RpcArchive to a short.", typeof(RpcArchive), typeof(short));
        }

        int IConvertible.ToInt32(IFormatProvider provider)
        {
            throw new RpcConversionFailureException("Cannot convert an RpcArchive to an int.", typeof(RpcArchive), typeof(int));
        }

        long IConvertible.ToInt64(IFormatProvider provider)
        {
            throw new RpcConversionFailureException("Cannot convert an RpcArchive to a long.", typeof(RpcArchive), typeof(long));
        }

        sbyte IConvertible.ToSByte(IFormatProvider provider)
        {
            throw new RpcConversionFailureException("Cannot convert an RpcArchive to an sbyte.", typeof(RpcArchive), typeof(sbyte));
        }

        float IConvertible.ToSingle(IFormatProvider provider)
        {
            throw new RpcConversionFailureException("Cannot convert an RpcArchive to a float.", typeof(RpcArchive), typeof(float));
        }

        string IConvertible.ToString(IFormatProvider provider)
        {
            throw new RpcConversionFailureException("Cannot convert an RpcArchive to a string.", typeof(RpcArchive), typeof(string));
        }

        object IConvertible.ToType(Type conversionType, IFormatProvider provider)
        {
            if (conversionType == null)
            {
                throw new ArgumentNullException("conversionType");
            }

            if (provider == null)
            {
                throw new ArgumentNullException("provider");
            }

            if (this.GetType() == conversionType)
            {
                return this;
            }

            try
            {
                DataContractJsonSerializerSettings settings = new DataContractJsonSerializerSettings();
                settings.EmitTypeInformation = EmitTypeInformation.Never;
                DataContractJsonSerializer archiveSerializer = new DataContractJsonSerializer(typeof(RpcArchive), settings);

                using (MemoryStream stream = new MemoryStream())
                {
                    archiveSerializer.WriteObject(stream, this);
                    stream.Position = 0;

                    DataContractJsonSerializer resultDeserializer = new DataContractJsonSerializer(conversionType);
                    return resultDeserializer.ReadObject(stream);
                }
            }
            catch (SerializationException ex)
            {
                throw new RpcConversionFailureException(string.Format(CultureInfo.InvariantCulture, "Cannot convert an RpcArchive to type: {0}.", conversionType.FullName), typeof(RpcArchive), conversionType, ex);
            }
            catch (InvalidDataContractException ex)
            {
                throw new RpcConversionFailureException(string.Format(CultureInfo.InvariantCulture, "Cannot convert an RpcArchive to type: {0}.", conversionType.FullName), typeof(RpcArchive), conversionType, ex);
            }
        }

        ushort IConvertible.ToUInt16(IFormatProvider provider)
        {
            throw new RpcConversionFailureException("Cannot convert an RpcArchive to a ushort.", typeof(RpcArchive), typeof(ushort));
        }

        uint IConvertible.ToUInt32(IFormatProvider provider)
        {
            throw new RpcConversionFailureException("Cannot convert an RpcArchive to a uint.", typeof(RpcArchive), typeof(uint));
        }

        ulong IConvertible.ToUInt64(IFormatProvider provider)
        {
            throw new RpcConversionFailureException("Cannot convert an RpcArchive to a ulong.", typeof(RpcArchive), typeof(ulong));
        }

        /// <summary>
        /// Retrieves a collection of all distinct types contained within this RpcArchive.  This is here so that serializers
        /// can be aware of all expected types prior to serialization.
        /// </summary>
        /// <returns>A collection of all distinct types contained within this RpcArchive.</returns>
        internal IEnumerable<Type> GetAllTypes()
        {
            return this.data.Values.Select(v => v.GetType()).Distinct();
        }

        /// <summary>
        /// An overload of the GetObjectData method that is required by the ISerializable interface.
        /// This is here so that inheritors of this class can access the GetObjectData method without
        /// having to cast their "this" pointer to an ISerializable.
        /// Populates a System.Runtime.Serialization.SerializationInfo with the data needed to serialize this object.
        /// </summary>
        /// <param name="info">The System.Runtime.Serialization.SerializationInfo to populate with data.</param>
        /// <param name="context">The destination (see System.Runtime.Serialization.StreamingContext) for this serialization.</param>
        protected virtual void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            if (info == null)
            {
                throw new ArgumentNullException("info");
            }

            // Store named values
            foreach (var keyValuePair in this.data)
            {
                info.AddValue(keyValuePair.Key, keyValuePair.Value);
            }

            // Store meta-data for named values encoded as value name followed by value type encoded as integer converted to string.
            // These are stored contiguously in an array, not using a dictionary.
            if (this.metaData.Count > 0)
            {
                string[] encodedMeta = new string[this.metaData.Keys.Count * 2];
                int n = 0;

                foreach (var key in this.data.Keys)
                {
                    encodedMeta[n++] = key;
                    encodedMeta[n++] = ((int)this.TypeToFieldType(this.metaData[key])).ToString(CultureInfo.InvariantCulture);
                }

                info.AddValue("__ftype", encodedMeta);
            }
        }

        /// <summary>
        /// Provides child classes with a mechanism to access the IConvertible.GetTypeCode method, as per Code Analysis requirements.
        /// Gets the underlying type code of this type.
        /// </summary>
        /// <returns>The underlying type code of this type.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "This is required by the IConvertible interface.")]
        protected TypeCode GetTypeCode()
        {
            return ((IConvertible)this).GetTypeCode();
        }

        /// <summary>
        /// Provides child classes with a mechanism to access the IConvertible.ToBoolean method, as per Code Analysis requirements.
        /// Converts this object to a boolean.
        /// </summary>
        /// <param name="provider">An IFormatProvider interface implementation that supplies culture-specific formatting information. </param>
        /// <returns>The boolean representation of this object.</returns>
        protected bool ToBoolean(IFormatProvider provider)
        {
            return ((IConvertible)this).ToBoolean(provider);
        }

        /// <summary>
        /// Provides child classes with a mechanism to access the IConvertible.ToByte method, as per Code Analysis requirements.
        /// Converts this object to a byte.
        /// </summary>
        /// <param name="provider">An IFormatProvider interface implementation that supplies culture-specific formatting information. </param>
        /// <returns>The byte representation of this object.</returns>
        protected byte ToByte(IFormatProvider provider)
        {
            return ((IConvertible)this).ToByte(provider);
        }

        /// <summary>
        /// Provides child classes with a mechanism to access the IConvertible.ToChar method, as per Code Analysis requirements.
        /// Converts this object to a char.
        /// </summary>
        /// <param name="provider">An IFormatProvider interface implementation that supplies culture-specific formatting information. </param>
        /// <returns>The char representation of this object.</returns>
        protected char ToChar(IFormatProvider provider)
        {
            return ((IConvertible)this).ToChar(provider);
        }

        /// <summary>
        /// Provides child classes with a mechanism to access the IConvertible.ToDateTime method, as per Code Analysis requirements.
        /// Converts this object to a DateTime.
        /// </summary>
        /// <param name="provider">An IFormatProvider interface implementation that supplies culture-specific formatting information. </param>
        /// <returns>The DateTime representation of this object.</returns>
        protected DateTime ToDateTime(IFormatProvider provider)
        {
            return ((IConvertible)this).ToDateTime(provider);
        }

        /// <summary>
        /// Provides child classes with a mechanism to access the IConvertible.ToDecimal method, as per Code Analysis requirements.
        /// Converts this object to a decimal.
        /// </summary>
        /// <param name="provider">An IFormatProvider interface implementation that supplies culture-specific formatting information. </param>
        /// <returns>The decimal representation of this object.</returns>
        protected decimal ToDecimal(IFormatProvider provider)
        {
            return ((IConvertible)this).ToDecimal(provider);
        }

        /// <summary>
        /// Provides child classes with a mechanism to access the IConvertible.ToDouble method, as per Code Analysis requirements.
        /// Converts this object to a double.
        /// </summary>
        /// <param name="provider">An IFormatProvider interface implementation that supplies culture-specific formatting information. </param>
        /// <returns>The double representation of this object.</returns>
        protected double ToDouble(IFormatProvider provider)
        {
            return ((IConvertible)this).ToDouble(provider);
        }

        /// <summary>
        /// Provides child classes with a mechanism to access the IConvertible.ToInt16 method, as per Code Analysis requirements.
        /// Converts this object to an Int16.
        /// </summary>
        /// <param name="provider">An IFormatProvider interface implementation that supplies culture-specific formatting information. </param>
        /// <returns>The Int16 representation of this object.</returns>
        protected short ToInt16(IFormatProvider provider)
        {
            return ((IConvertible)this).ToInt16(provider);
        }

        /// <summary>
        /// Provides child classes with a mechanism to access the IConvertible.ToInt32 method, as per Code Analysis requirements.
        /// Converts this object to a Int32.
        /// </summary>
        /// <param name="provider">An IFormatProvider interface implementation that supplies culture-specific formatting information. </param>
        /// <returns>The Int32 representation of this object.</returns>
        protected int ToInt32(IFormatProvider provider)
        {
            return ((IConvertible)this).ToInt32(provider);
        }

        /// <summary>
        /// Provides child classes with a mechanism to access the IConvertible.ToInt64 method, as per Code Analysis requirements.
        /// Converts this object to a Int64.
        /// </summary>
        /// <param name="provider">An IFormatProvider interface implementation that supplies culture-specific formatting information. </param>
        /// <returns>The Int64 representation of this object.</returns>
        protected long ToInt64(IFormatProvider provider)
        {
            return ((IConvertible)this).ToInt64(provider);
        }

        /// <summary>
        /// Provides child classes with a mechanism to access the IConvertible.ToSByte method, as per Code Analysis requirements.
        /// Converts this object to a sbyte.
        /// </summary>
        /// <param name="provider">An IFormatProvider interface implementation that supplies culture-specific formatting information. </param>
        /// <returns>The sbyte representation of this object.</returns>
        protected sbyte ToSByte(IFormatProvider provider)
        {
            return ((IConvertible)this).ToSByte(provider);
        }

        /// <summary>
        /// Provides child classes with a mechanism to access the IConvertible.ToSingle method, as per Code Analysis requirements.
        /// Converts this object to a Single.
        /// </summary>
        /// <param name="provider">An IFormatProvider interface implementation that supplies culture-specific formatting information. </param>
        /// <returns>The Single representation of this object.</returns>
        protected float ToSingle(IFormatProvider provider)
        {
            return ((IConvertible)this).ToSingle(provider);
        }

        /// <summary>
        /// Provides child classes with a mechanism to access the IConvertible.ToString method, as per Code Analysis requirements.
        /// Converts this object to a string.
        /// </summary>
        /// <param name="provider">An IFormatProvider interface implementation that supplies culture-specific formatting information. </param>
        /// <returns>The string representation of this object.</returns>
        protected string ToString(IFormatProvider provider)
        {
            return ((IConvertible)this).ToString(provider);
        }

        /// <summary>
        /// Provides child classes with a mechanism to access the IConvertible.ToType method, as per Code Analysis requirements.
        /// Converts this object to the type given by the <paramref name="conversionType"/> parameter.
        /// </summary>
        /// <param name="conversionType">The target type that this object should be converted into.</param>
        /// <param name="provider">An IFormatProvider interface implementation that supplies culture-specific formatting information. </param>
        /// <returns>The string representation of this object.</returns>
        protected object ToType(Type conversionType, IFormatProvider provider)
        {
            return ((IConvertible)this).ToType(conversionType, provider);
        }

        /// <summary>
        /// Provides child classes with a mechanism to access the IConvertible.ToUInt16 method, as per Code Analysis requirements.
        /// Converts this object to a UInt16.
        /// </summary>
        /// <param name="provider">An IFormatProvider interface implementation that supplies culture-specific formatting information. </param>
        /// <returns>The UInt16 representation of this object.</returns>
        protected ushort ToUInt16(IFormatProvider provider)
        {
            return ((IConvertible)this).ToUInt16(provider);
        }

        /// <summary>
        /// Provides child classes with a mechanism to access the IConvertible.ToUInt32 method, as per Code Analysis requirements.
        /// Converts this object to a UInt32.
        /// </summary>
        /// <param name="provider">An IFormatProvider interface implementation that supplies culture-specific formatting information. </param>
        /// <returns>The UInt32 representation of this object.</returns>
        protected uint ToUInt32(IFormatProvider provider)
        {
            return ((IConvertible)this).ToUInt32(provider);
        }

        /// <summary>
        /// Provides child classes with a mechanism to access the IConvertible.ToUInt64 method, as per Code Analysis requirements.
        /// Converts this object to a UInt64.
        /// </summary>
        /// <param name="provider">An IFormatProvider interface implementation that supplies culture-specific formatting information. </param>
        /// <returns>The UInt64 representation of this object.</returns>
        protected ulong ToUInt64(IFormatProvider provider)
        {
            return ((IConvertible)this).ToUInt64(provider);
        }

        /// <summary>
        /// Converts a field type enumeration to a Reflection type.
        /// </summary>
        /// <param name="fieldType">The field type to convert.</param>
        /// <returns>The Reflection type.</returns>
        private Type FieldTypeToType(FieldType fieldType)
        {
            switch (fieldType)
            {
                case FieldType.Archive:
                    return typeof(RpcArchive);
                case FieldType.String:
                    return typeof(string);
                case FieldType.Bool:
                    return typeof(bool);
                case FieldType.Int8:
                    return typeof(sbyte);
                case FieldType.UInt8:
                    return typeof(byte);
                case FieldType.Char:
                    // Actually translates into sbyte, but using char to match names based on usage rather than bit width
                    return typeof(char);
                case FieldType.UShort:
                    return typeof(ushort);
                case FieldType.Short:
                    return typeof(short);
                case FieldType.Int:
                    return typeof(int);
                case FieldType.UInt:
                    return typeof(uint);
                case FieldType.Long:
                    return typeof(int);
                case FieldType.ULong:
                    return typeof(uint);
                case FieldType.Int64:
                    return typeof(long);
                case FieldType.UInt64:
                    return typeof(ulong);
                case FieldType.Float:
                    return typeof(float);
                case FieldType.Double:
                    return typeof(double);
                case FieldType.Blob:
                    return typeof(RpcBinaryData);
                default:
                    return null;
            }
        }

        /// <summary>
        /// Converts a Reflection type into a field type enumeration.
        /// </summary>
        /// <param name="type">The Reflection type to convert.</param>
        /// <returns>The enumeration type.</returns>
        private FieldType TypeToFieldType(Type type)
        {
            if (type == typeof(RpcArchive))
            {
                return FieldType.Archive;
            }
            else if (type == typeof(string))
            {
                return FieldType.String;
            }
            else if (type == typeof(bool))
            {
                return FieldType.Bool;
            }
            else if (type == typeof(sbyte))
            {
                return FieldType.Int8;
            }
            else if (type == typeof(byte))
            {
                return FieldType.UInt8;
            }
            else if (type == typeof(char))
            {
                return FieldType.Int;
            }
            else if (type == typeof(ushort))
            {
                return FieldType.UShort;
            }
            else if (type == typeof(short))
            {
                return FieldType.Short;
            }
            else if (type == typeof(int))
            {
                return FieldType.Int;
            }
            else if (type == typeof(uint))
            {
                return FieldType.UInt;
            }
            else if (type == typeof(long))
            {
                return FieldType.Int64;
            }
            else if (type == typeof(ulong))
            {
                return FieldType.UInt64;
            }
            else if (type == typeof(float))
            {
                return FieldType.Float;
            }
            else if (type == typeof(double))
            {
                return FieldType.Double;
            }
            else if (type == typeof(RpcBinaryData))
            {
                return FieldType.Blob;
            }

            return FieldType.Unknown;
        }
    }
}
